use crate::coordinate::Coord;
use std::num::NonZeroUsize;

/// 矩形盘面，允许空洞
///
/// 该类型抽象了一个矩形网格的盘面。
/// 其中，有的位置是空洞，有的位置非空。
/// 它是不可变的，使用[`LayoutBuilder`]以构造。
///
/// 但它不存放真正的内容，而是一对一地赋予每个非空的格子一个`[0, n)`的索引。
/// 索引和坐标可以互相获取。用户可以使用长度为`n`的数组存储格子的信息。
///
/// 图像如下，中间的数字是格子的索引：
///
/// ```text
///   _ 0 1 2 3 x
/// 0 |   0 1 2
/// 1 | 3 4 5 6
/// 2 | 7 8 9
/// y
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Layout {
    /// 列数
    ///
    /// `x`坐标必定小于它。
    columns: NonZeroUsize,
    /// 非空格子的位置
    ///
    /// 第`i`项为`j`，说明第`i`个非空格子在`field`的第`j`项。
    /// 即，`field[cells_idx[i]] = Some(i)`与`cells_idx[field[j]] = j`。
    /// 进而，可以获取第`i`个非空格子的坐标。
    cells: Box<[usize]>,
    /// 每个坐标的格子
    ///
    /// 第`x + y * columns`项，存放对应坐标的信息。
    /// 若为[`Some(i)`]，说明该坐标是第`i`个非空的格子，格子索引为`i`。
    /// 若为[`None`]，说明该坐标为空。
    ///
    /// 它的长度应为`columns`的倍数。
    field: Box<[Option<usize>]>,
}

impl Layout {
    /// 获取列数
    ///
    /// 返回值非零。
    fn columns(&self) -> usize {
        self.columns.get()
    }

    /// 非空格子的数量
    pub fn cell_count(&self) -> usize {
        self.cells.len()
    }

    /// 迭代矩形的盘面
    ///
    /// 返回值是迭代器，迭代每一行；每一行是切片，包含着对应格子的索引。
    pub fn iter_rect(&self) -> impl Iterator<Item = &'_ [Option<usize>]> + '_ {
        self.field.chunks(self.columns())
    }

    /// 获取格子的索引
    ///
    /// 给定坐标，获取其对应的格子的索引。
    pub fn get_index(&self, Coord { x, y }: Coord) -> Option<usize> {
        let x: usize = x.try_into().ok()?;
        (x < self.columns()).then_some(())?;
        let y: usize = y.try_into().ok()?;
        *self.field.get(x + y * self.columns())?
    }

    /// 获取位置的坐标
    ///
    /// “位置”指`field`的下标。
    ///
    /// 如果`pos`不是有效的下标，本函数不会报错，但返回值也不会是有效的坐标。
    fn get_coord_of_pos(&self, pos: usize) -> Coord {
        Coord {
            x: (pos % self.columns()) as i32,
            y: (pos / self.columns()) as i32,
        }
    }

    /// 迭代非空格子
    ///
    /// 迭代每个非空格子的坐标。格子的索引就是迭代中的位置。
    pub fn iter_cells(&self) -> impl Iterator<Item = Coord> + '_ {
        self.cells.iter().map(|&pos| self.get_coord_of_pos(pos))
    }

    /// 获取坐标
    ///
    /// 给定非空格子的索引，返回其坐标。
    pub fn get_coord(&self, index: usize) -> Option<Coord> {
        let &pos = self.cells.get(index)?;
        Some(self.get_coord_of_pos(pos))
    }
}

pub struct Builder {
    columns: NonZeroUsize,
    cells: Vec<usize>,
    field: Vec<Option<usize>>,
}

impl Builder {
    pub fn new(columns: NonZeroUsize) -> Self {
        Self {
            columns,
            cells: Vec::new(),
            field: Vec::new(),
        }
    }

    /// 获取列数
    ///
    /// 返回值非零。
    fn columns(&self) -> usize {
        self.columns.get()
    }

    /// 开始新行
    ///
    /// 添加至少一个空洞，到达最近的行尾。
    fn next_line(&mut self) {
        let new_len = (self.field.len() / self.columns() + 1) * self.columns();
        self.field.resize(new_len, None);
    }

    /// 结束一行
    ///
    /// 添加一些空洞，到达最近的行尾；或啥都不干，如就在行尾。
    pub fn end_line(&mut self) {
        if self.field.len() % self.columns() != 0 {
            self.next_line(); // 如果不是整行，那么换行
        }
    }

    pub fn push_hole(&mut self) {
        self.field.push(None);
    }

    pub fn push_cell(&mut self) -> usize {
        let index = self.cells.len();
        self.cells.push(self.field.len());
        self.field.push(Some(index));
        index
    }

    pub fn build(mut self) -> Layout {
        self.end_line();
        Layout {
            columns: self.columns,
            cells: self.cells.into_boxed_slice(),
            field: self.field.into_boxed_slice(),
        }
    }
}
